require "TSLib"
if ts == nil then    
    ts = require("ts")
end

-- baiduAPI Authentication
local _data = require("arknights/data")
-- local indext = addTSOcrDictEx(ocr.tab)
local _, access_token = getAccessToken(_data.API, _data.KEY)

function mySleep(ms)
    local scale = 1
    mSleep(ms*scale)
end

function doAt(time_strs, action, waitAction)
    while true do
        act = false
        local curtime = os.date("%H:%M")
        for _, str in ipairs(time_strs) do
            act = act or (str == curtime)
        end
        if act then 
            local success = xpcall(action, function (err)
                log("[DATE] error: "..err, "doAt_log")
                log(debug.traceback(), "doAt_log")
            end)
            if success then
                log(string.format("[DATE] scheduled execution @%s succeeded", curtime), "doAt_log")
            else
                log(string.format("[DATE] scheduled execution @%s failed", curtime), "doAt_log")
            end
            while curtime == os.date("%H:%M") do
                mSleep(5000)
            end
        end

        toast("current time: "..curtime)
        if type(waitAction) == "function" then
            waitAction()
        end
        mSleep(20*1000)
    end
end

-- execute different function at different time
-- expected ta_table format:
--  * ta_table = { [str] : Union(Func, {"act":Func, "rand":int}) }
function doAllAt(ta_table, refresh_time, waitAction)
    -- flat ta_table to a processable linear format and randomize time on demand
    local function time_refresh()
        local ret = {}
        local info = {}

        local function to_time_string(val, base)
            -- make correct format for time strings (h/m/s) and returns the c
            local mod_val = ( val + base ) % base
            if mod_val < 10 then
                mod_val = "0"..mod_val
            end
            return mod_val, math.floor(val / base)
        end
        
        local function randomize_time(time_str, r)
            local h = tonumber(string.sub(time_str, 1, 2))
            local m = tonumber(string.sub(time_str, 4))
            local d_m = math.random(-r, r)
            local new_m, d_h = to_time_string(m + d_m, 60)
            local new_h = to_time_string(h + d_h, 24)

            return new_h..":"..new_m
        end

        for k, v in pairs(ta_table) do
            if type(v) == "table" then
                for _, kk in ipairs(k) do
                    local new_time = randomize_time(kk, v.rand)
                    ret[new_time] = v.act
                    table.insert(info, new_time)
                end
            else
                for _, kk in ipairs(k) do
                    ret[kk] = v
                    table.insert(info, kk)
                end
            end
        end
        toast("randomized time: "..table.concat(info,", "), 3)
        return ret
    end
    -- randomize range time at the beginning
    local next_ta = time_refresh()

    while true do
        local curtime = os.date("%H:%M")
        -- refresh randomized time everyday
        if curtime == refresh_time then
            next_ta = time_refresh()
        end
        -- check time
        for time_str, action in pairs(next_ta) do
            if time_str == curtime then
                log(string.format("[DATE] scheduled execution @%s started", time_str), "doAllAt_log")
                local success = xpcall(action, function (err)
                    log(string.format("[DATE] error: %s", err), "doAllAt_log")
                    log(debug.traceback(), "doAllAt_log")
                end)
                if success then
                    log(string.format("[DATE] scheduled execution @%s succeeded", time_str), "doAllAt_log")
                else
                    log(string.format("[DATE] scheduled execution @%s failed", time_str), "doAllAt_log")
                end
                while curtime == os.date("%H:%M") do
                    mSleep(5000)
                end
            end
        end

        toast("current time: "..curtime)
        if type(waitAction) == 'function' then
            waitAction()
        end
        mSleep(20*1000)
    end
end

-- my tap with picture cursor
function randTap(x,y,r,ms)
   x = x + math.random(-r,r);
   y = y + math.random(-r,r);
   tap(x,y,ms,"cursor.png");
end

function qcRandTap(point, r, cooldown)
    if type(r) ~= "number" then 
        r = 5
    end
    randTap(point.x, point.y, r, 50)
    if type(cooldown) == "number" then
        mySleep(cooldown)
    end
end

function qcKeyBack(cooldown)
    os.execute("input keyevent KEYCODE_BACK")
    if type(cooldown) == "number" then
        mySleep(cooldown)
    end
end

-- this function assumes that touchDown is already called
function drag(px, py, dx, dy, interval)
    local ddx = dx / interval
    local ddy = dy / interval
    for i = 0,interval do
        px = px + ddx
        py = py + ddy
        touchMove(px, py)
    end
    mSleep(130)
    return px, py
end

-- an unpercise version of drag
function swipe(from, to, interval)
    if interval == nil then
        interval = 300
    end
    os.execute("input swipe "..from.x.." "..from.y.." "..to.x.." "..to.y .. " " .. interval)
end

-- short hand color waiter
function qcIsColor(cpoint)
    return isColor(cpoint.x, cpoint.y, cpoint.c)
end

function qcAllIsColor(cplist)
    for i, cp in ipairs(cplist) do
        if not qcIsColor(cp) then
            return false
        end
    end
    return true
end

function _wait_for_cond(cond, ms, action, timeout, timeout_act)
    local cnt = 0
    
    if timeout == nil then
        timeout = 6e5 -- 10 min
    end

    if timeout_act == nil then
        timeout_act = function ()
            error("expected condition timed out")
        end
    end

    mySleep(ms)
    repeat
        if type(action) == "function" and cnt % 4 == 0 then
            local ret = action()
            if ret == false then 
                return
            end
        end
        cnt = cnt + 1
        mySleep(ms)
        if cnt * ms > timeout then 
            timeout_act()
        end
    until cond()
end

function waitForColor(cpoint, ms, action)
    _wait_for_cond(
        function ()
            return qcIsColor(cpoint)
        end,
        ms, action
    )
end

function waitWhileColor(cpoint, ms, action)
    _wait_for_cond(
        function()
            return not qcIsColor(cpoint)
        end,
        ms, action
    )
end

function waitForAllColors(cplist, ms, action)
    _wait_for_cond(
        function ()
            return qcAllIsColor(cplist)
        end,
        ms, action
    )
end

function waitWhileAllColors(cplist, ms, action)
    _wait_for_cond(
        function()
            return not qcAllIsColor(cplist)
        end,
        ms, action
    )
end

function waitWhileBlackScreen(interval)
    _wait_for_cond(
        function ()
            local x = math.random(0, 720)
            local y = math.random(0, 480)
            return not isColor(x, y, 0)
        end, 
        interval
    )
end

function waitForLuminOver(cpoint, thresh, ms, action)
    _wait_for_cond(
        function()
            return getLumin(cpoint.x, cpoint.y) >= thresh
        end,
        ms, action
    )
end

function waitForLuminUnder(cpoint, thresh, ms, action)
    _wait_for_cond(
        function ()
            return getLumin(cpoint.x, cpoint.y) <= thresh
        end,
        ms, action
    )
end

function waitForText(text_complex, ms, action)
    _wait_for_cond(
        function ()
            return text_complex.text == baiduOcr(text_complex.x1, text_complex.y1,
                                                 text_complex.x2, text_complex.y2)        
        end, ms, action )
end

function waitForPattern(snap_rect, template_file, diff_thresh, ms, action)
    _wait_for_cond(
        function ()
            return foundPattern(snap_rect, template_file, diff_thresh)    
        end,
        ms, action )
end

-- table lib extend: check whether a list contains a value
function has(list, val)
    for _, v in ipairs(list) do
        if val == v then
            return true
        end
    end
    return false
end

-- table lib extend: find the index of a value, -1 if not found
function index(list, val)
    for i, v in ipairs(list) do
        if val == v then
            return i
        end
    end
    return -1
end

-- table lib extend: delete the 1st value found in the list, unchanged if not found
function delete(list, val)
    for i, v in ipairs(list) do
        if v == val then
            table.remove(list, i)
            return 
        end
    end
end

-- no nil value allowed!
function hasKey(tab, val)
    return tab[val] ~= nil
end

function subtab(tab, start, fin)
    local ret = {}
    if start < 0 then
        start = #tab + start
    end
    if fin == nil then
        fin = #tab
    elseif fin < 0 then
        fin = #tab + fin
    end
    for i = start,fin do
        table.insert( ret, tab[i] )
    end
    -- dialog(table.concat( ret, ", " ))
    return ret
end

function mergetab(...)
    local tabs = {...}
    if not tabs then
        return {}
    end
    local retval = {}
    for i = 1,#tabs do
        if tabs[i] then
            for k,v in pairs(tabs[i]) do
                retval[k] = v
            end
        end
    end
    return retval
end

function mergelist(...)
    local lists = {...}
    if not lists then
        return {}
    end
    local retval = {}
    for i = 1,#lists do
        if lists[i] then
            for _, v in ipairs(lists[i]) do
                table.insert(retval, v)
            end
        end
    end
    return retval
end

-- Luminancy function
function getLumin(x, y)
    local r, g, b = getColorRGB(x, y)
    return (r + g + b) / 3
end

-- edge fixing algining function with luminancy
function fix_coordinate(x, y, dx, dy, dark_thresh, light_thresh)
    -- x, y should be the dark side of the edge
    -- You should garantee that a fix is validate by offering only on non-zero delta
    if dy == 0 then
        local c1 = getLumin(x-dx, y)
        local c2 = getLumin(x, y)
        while c1 > light_thresh and c2 > light_thresh do -- 150 by default
            x = x + dx
            c1 = c2
            c2 = getLumin(x, y)
        end
        while c1 < dark_thresh and c2 < dark_thresh do -- 80 by default
            x = x - dx
            c2 = c1
            c1 = getLumin(x-dx, y)
        end
        return x, y
    end
    if dx == 0 then
        local c1 = getLumin(x, y-dy)
        local c2 = getLumin(x, y)
        while c1 > light_thresh and c2 > light_thresh do -- 150 by default
            y = y + dy
            c1 = c2
            c2 = getLumin(x, y)
        end
        while c1 < dark_thresh and c2 < dark_thresh do -- 80 by default
            y = y - dy
            c2 = c1
            c1 = getLumin(x, y-dy)
        end
        return x, y
    end
end


function fix_position(x, y, dx, dy, mx, my, dark_thresh, light_thresh)
    -- drag the content so x is on the dark side of an edge
    -- drag delta dx/dy is totally different from serch delta 
    --[[ the last retval refers to that whether this function success in trial steps, if not, 
            it is likely that the inital touchdown is not recorded and should be done again ]]
    local trial = 1920 -- 划了一屏还没找到肯定有问题
    if dy == 0 then
        local c1 = getLumin(x-dx, y)
        local c2 = getLumin(x, y)
        -- dialog(string.format("c1 %d, c2 %d", c1, c2))
        while not (c1 > light_thresh and c2 < dark_thresh) do -- 150 by default
            mx = mx - dx
            touchMove(mx, my)
            c1 = c2
            c2 = getLumin(x, y)
            trial = trial - 1
            if trial == 0 then
                return mx, my, false
            end
        end
        return mx, my, true
    end
    if dx == 0 then 
        local c1 = getLumin(x, y-dy)
        local c2 = getLumin(x, y)
        while not (c1 > light_thresh and c2 < dark_thresh) do -- 150 by default
            my = my - dy
            touchMove(mx, my)
            c1 = c2
            c2 = getLumin(x, y)
            trial = trial - 1
            if trial == 0 then
                return mx, my, false
            end
        end
        return mx, my, true
    end
end

-- use luminancy to check wether the scroll is at an end
function scroll_condition_dark(cond)
    return getLumin(cond[1], cond[2]) < cond[3]
end

function scroll_condition_light(cond)
    return getLumin(cond[1], cond[2]) > cond[3]
end

function foundPattern(region, pat_file, diff_thresh)
    if cv == nil then
        cv = require("libluacv")
    end
    local snap_file = userPath() .. "/res/tmp.png"
    snapshot(snap_file, region.x1, region.y1, region.x2, region.y2)
    local p,x,y = cv.cvMatchTemplate(snap_file, pat_file)
    return p < diff_thresh, x, y
end

-- file system operations
function lsdir(path)
    local a = io.popen("ls "..path);
    local f = {};
    for l in a:lines() do
        table.insert(f,l)
    end
    a:close()
    return f
end

function movefile(path,to)
    os.execute("mv "..path.." "..to);
end

function point_close_enough(coord1, coord2)
    return math.sqrt(math.pow(coord1[1]-coord2[1], 2) + math.pow(coord1[2] - coord2[2],2)) < 10
end


-- baiduAI 
function baiduOcr(x1, y1, x2, y2)
    local token = _data.access_token
    local snap_file = userPath() .. "/res/tmp.png"
    -- local snap_bin_file = string.gsub(snap_file, ".png", "_bin.png")
    snapshot(snap_file, x1, y1, x2, y2)
    -- imageBinaryzation(snap_file, 120)
    local tab={
        detect_direction="false",
        detect_language="false",
        paragraph="false",
        probability="true"
    }
    local status, json_str = baiduAI(token, snap_file, tab)
    if status then
        local obj = ts.json.decode(json_str)
        if #obj.words_result >= 1 then
            return obj.words_result[1].words
        else 
            dialog("no text found, mannual adjustment required!!",1)
            mSleep(2000)
            return nil
        end
    else
        return nil
    end
end

-- init(0) -- standard screen (x=short,y=long for phones & reverse for tablets)
-- init(1) -- anti-clockwise reversed 90
